package top.gytf.unifiedcall.producer.support.exception;

import cn.hutool.core.lang.Pair;
import org.springframework.util.Assert;
import top.gytf.unifiedcall.model.Sortable;
import top.gytf.unifiedcall.model.exception.BaseException;

import java.util.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 组合一组异常适配器
 */
public class CompositeExceptionAdapter implements ExceptionAdapter {
    private final List<ExceptionAdapter> adapters;
    private final int order;

    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Map<Class<? extends Throwable>, Integer> scoreCache = new HashMap<>();
    private final Map<Class<? extends Throwable>, ExceptionAdapter> mappingCache = new HashMap<>();

    public CompositeExceptionAdapter() {
        this(Collections.emptyList());
    }

    public CompositeExceptionAdapter(List<ExceptionAdapter> exceptionAdapters) {
        this(exceptionAdapters, 0);
    }

    public CompositeExceptionAdapter(List<ExceptionAdapter> exceptionAdapters, int order) {
        this.adapters = new ArrayList<>(exceptionAdapters.stream()
                .sorted(Comparator.comparingInt(Sortable::order))
                .toList());
        this.order = order;
    }

    @Override
    public int order() {
        return order;
    }

    @Override
    public int calcSupportScore(Class<? extends Throwable> cls) {
        Pair<Integer, ExceptionAdapter> adapterInfo = selectAdapter(cls);
        if (adapterInfo.getKey() < 0) {
            return adapterInfo.getKey();
        }
        return adapterInfo.getValue() != null ? adapterInfo.getKey() : -1;
    }

    @Override
    public BaseException convert(Throwable throwable) {
        Pair<Integer, ExceptionAdapter> adapterInfo = selectAdapter(throwable.getClass());
        Assert.state(adapterInfo.getKey() >= 0 && adapterInfo.getValue() != null,
                "未支持适配异常：" + throwable.getClass());
        return adapterInfo.getValue().convert(throwable);
    }

    /**
     * 添加一个适配器
     */
    public void addAdapter(ExceptionAdapter adapter) {
        lock.writeLock().lock();
        try {
            int o = adapter.order();
            int idx = 0;
            int size = adapters.size();
            for (; idx < size; idx++) {
                ExceptionAdapter a = adapters.get(idx);
                if (a.equals(adapter)) {
                    // 忽略重复添加
                    return;
                } else if (a.order() > o) {
                    // 找到了合适的位置
                    break;
                }
            }
            adapters.add(idx, adapter);
            invalidateCache();
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * 移除适配器
     */
    public void removeAdapter(ExceptionAdapter adapter) {
        lock.writeLock().lock();
        try {
            adapters.remove(adapter);
            invalidateCache();
        } finally {
            lock.writeLock().unlock();
        }
    }

    /**
     * 清空缓存
     */
    public void invalidateCache() {
        lock.writeLock().lock();
        try {
            this.scoreCache.clear();
            this.mappingCache.clear();
        } finally {
            lock.writeLock().unlock();
        }
    }

    private Pair<Integer, ExceptionAdapter> selectAdapter(Class<? extends Throwable> cls) {
        // 直接查看有没有查找过，存在查询缓存的直接返回
        lock.readLock().lock();
        try {
            Integer score = this.scoreCache.get(cls);
            if (score != null) {
                return Pair.of(score, mappingCache.get(cls));
            }
        } finally {
            lock.readLock().unlock();
        }

        // 未查询，需要查询
        lock.writeLock().lock();
        try {
            // 再次检查是否查询过
            Integer score = this.scoreCache.get(cls);
            if (score == null) {
                // 遍历适配器，获取最合适且支持的适配器
                ExceptionAdapter supportedAdapter = null;
                score = -1;
                for (ExceptionAdapter adapter : adapters) {
                    int adapterScore = adapter.calcSupportScore(cls);
                    if (adapterScore > score) {
                        score = adapterScore;
                        supportedAdapter = adapter;
                    }
                }
                if (supportedAdapter != null) {
                    mappingCache.put(cls, supportedAdapter);
                }
                this.scoreCache.put(cls, score);
                return Pair.of(score, supportedAdapter);
            } else {
                return Pair.of(score, mappingCache.get(cls));
            }
        } finally {
            lock.writeLock().unlock();
        }
    }
}